﻿using Fst.SemiRing;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;

namespace Fst
{
    /// <summary>
    /// An immutable finite state transducer implementation.
    /// 
    /// Holds a fixed size array of {@link edu.cmu.sphinx.fst.ImmutableState} objects
    /// not allowing additions/deletions
    /// </summary>
    public class ImmutableFst: Fst
    {
        // fst states
        private ImmutableState[] states = null;

        // number of states
        private int _numStates;

        /**
        /// Default private constructor.
        /// 
        /// An ImmutableFst cannot be created directly. It needs to be deserialized.
        /// 
        /// @see edu.cmu.sphinx.fst.ImmutableFst#loadModel(String)
         */
        private ImmutableFst() {

        }

        /**
        /// Private Constructor specifying the capacity of the states array
        /// 
        /// An ImmutableFst cannot be created directly. It needs to be deserialized.
        /// 
        /// @see edu.cmu.sphinx.fst.ImmutableFst#loadModel(String)
        /// 
        /// @param numStates
        ///            the number of fst's states
         */
        private ImmutableFst(int numStates) 
            :base(0)
        {
            this._numStates = numStates;
            this.states = new ImmutableState[numStates];
        }

        /*
        /// (non-Javadoc)
        /// 
        /// @see edu.cmu.sphinx.fst.Fst#getNumStates()
         */
        public int getNumStates() 
        {
            return this._numStates;
        }

        /*
        /// (non-Javadoc)
        /// 
        /// @see edu.cmu.sphinx.fst.Fst#getState(int)
         */
        public ImmutableState getState(int index) 
        {
            return states[index];
        }

        /*
        /// (non-Javadoc)
        /// 
        /// @see edu.cmu.sphinx.fst.Fst#addState(edu.cmu.sphinx.fst.State)
         */
        public void addState(State state) 
        {
            throw new InvalidOperationException("You cannot modify an ImmutableFst.");
        }

        /*
        /// (non-Javadoc)
        /// 
        /// @see edu.cmu.sphinx.fst.Fst#saveModel(java.lang.String)
         */
        public void saveModel(String filename)
        {
            throw new InvalidOperationException("You cannot serialize an ImmutableFst.");
        }

        /**
        /// Deserializes an ImmutableFst from an ObjectInputStream
        /// 
        /// @param in
        ///            the ObjectInputStream. It should be already be initialized by
        ///            the caller.
        /// @return
        /// @throws IOException
        /// @throws ClassNotFoundException
         */
        private static ImmutableFst readImmutableFst(FileStream inStream)
        {
            BinaryFormatter serializer = new BinaryFormatter();
            String[] _is = readStringMap(inStream);
            String[] os = readStringMap(inStream);
            int startid = (int)serializer.Deserialize(inStream);
            Semiring semiring = (Semiring) serializer.Deserialize(inStream);
            int numStates = (int)serializer.Deserialize(inStream);
            ImmutableFst res = new ImmutableFst(numStates);
            res.isyms = _is;
            res.osyms = os;
            res._semiring = semiring;
            for (int i = 0; i < numStates; i++) 
            {
                int numArcs = (int)serializer.Deserialize(inStream);
                ImmutableState s = new ImmutableState(numArcs + 1);
                float f = (float)serializer.Deserialize(inStream);
                if (f == res._semiring.zero()) {
                    f = res._semiring.zero();
                } else if (f == res._semiring.one()) 
                {
                    f = res._semiring.one();
                }
                s.setFinalWeight(f);
                s.id = (int)serializer.Deserialize(inStream);
                res.states[s.getId()] = s;
            }
            res.setStart(res.states[startid]);

            numStates = res.states.Length;
            for (int i = 0; i < numStates; i++) 
            {
                ImmutableState s1 = res.states[i];
                for (int j = 0; j < s1.initialNumArcs - 1; j++) {
                    Arc a = new Arc();
                    a.setIlabel((int)serializer.Deserialize(inStream));
                    a.setOlabel((int)serializer.Deserialize(inStream));
                    a.setWeight((float)serializer.Deserialize(inStream));
                    a.setNextState(res.states[(int)serializer.Deserialize(inStream)]);
                    s1.setArc(j, a);
                }
            }

            return res;
        }

  
        /// <summary>
        /// Deserializes an ImmutableFst from disk
        /// </summary>
        /// <param name="filename">the binary model filename</param>
        /// <returns></returns>
        new public static ImmutableFst loadModel(String filename) 
        {
            ImmutableFst obj=null;

            try {
                FileStream fis = new FileStream(filename, FileMode.Open);
                obj = readImmutableFst(fis);
                fis.Close();
            } 
            catch (FileNotFoundException e) {
                Trace.Write(e);
            } 
            catch (IOException e) 
            {
                Trace.Write(e);
            } 
            catch (Exception e) 
            {
                Trace.Write(e);
            }

            //return an empty object not null
            return obj==null?new ImmutableFst():obj;
        }
        
        /*
        /// (non-Javadoc)
        /// 
        /// @see edu.cmu.sphinx.fst.Fst#deleteState(edu.cmu.sphinx.fst.State)
         */
        public void deleteState(State state) 
        {
            throw new InvalidOperationException("You cannot modify an ImmutableFst.");
        }

        /*
        /// (non-Javadoc)
        /// 
        /// @see edu.cmu.sphinx.fst.Fst#toString()
         */
        override
        public String ToString() 
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("Fst(start=" + start + ", isyms=" + isyms + ", osyms="
                    + osyms + ", semiring=" + _semiring + ")\n");
            int numStates = states.Length;
            for (int i = 0; i < numStates; i++) 
            {
                State s = states[i];
                sb.Append("  " + s + "\n");
                int numArcs = s.getNumArcs();
                for (int j = 0; j < numArcs; j++) {
                    Arc a = s.getArc(j);
                    sb.Append("    " + a + "\n");
                }
            }

            return sb.ToString();
        }

        /*
        /// (non-Javadoc)
        /// 
        /// @see java.lang.Object#equals(java.lang.Object)
         */
        override
        public Boolean Equals(Object obj) {
            if (this == obj)
                return true;
            if (GetType() != obj.GetType())
                return false;
            ImmutableFst other = (ImmutableFst) obj;
            if (!states.Equals(other.states))
                return false;
            if (!base.Equals(obj))
                return false;
            return true;
        }
    }
}
